#!/usr/bin/env python

'''Chachamaru

@author Julot <andy.julot@gmail.com>
@since Thursday, June 3rd, 2011. 01:56 PM'''

import os
import sys
import time
import model
import signal
import miniboa
import colorama
import datetime

from serial import Serial
from ConfigParser import ConfigParser
from autorm.db.connection import autorm_db, Database

success = lambda x: sys.stdout.write(colorama.Fore.GREEN +
                                     colorama.Style.BRIGHT + x)
progress = lambda x: sys.stdout.write(colorama.Fore.CYAN +
                                      colorama.Style.BRIGHT + x)
warning = lambda x: sys.stdout.write(colorama.Fore.YELLOW +
                                     colorama.Style.BRIGHT + x)
fail = lambda x: sys.stdout.write(colorama.Fore.RED +
                                  colorama.Style.BRIGHT + x)
info = lambda x: sys.stdout.write(colorama.Fore.WHITE +
                                  colorama.Style.BRIGHT + x)
normal = lambda x: sys.stdout.write(x)
ask = lambda x: raw_input(colorama.Fore.WHITE + colorama.Style.BRIGHT + 
                          '%s [yes|no] ' % x)[0].lower() == 'y'

class Meta(object):
    '''Meta class
    
    @author Julot <andy.julot@gmail.com>
    @since Saturday, September 24, 2011. 02:26 PM'''
    
    def __init__(self, **kw):
        self.name = 'Chachamaru'
        self.description = 'Abip Smart Home Telnet Server'
        self.trademarks = 'As I pray, unlimited syntax works'
        self.copyright = 'Copyright \xa9 2010 Abip'
        self.company_name = 'Abip'
        s  = 'May you do good and not evil.\r\n'
        s += 'May you find forgiveness for yourself and forgive others.\r\n'
        s += 'May you share freely, never taking more than you give.'
        self.comments = s
        self.script = '%s.py' % self.name.lower()
        path = os.path.join(os.getcwdu(), "resource", "icon", 
                            '%s.ico' % self.name.lower())
        self.icon_resources = [(1, path)]
        self.dest_base = '..\%s' % self.name
        
        self.author = 'Julot'
        self.author_email = 'andy.julot@gmail.com'
        self.maintainer = 'Julot'
        self.maintainer_email = 'andy.julot@gmail.com'
        self.url = 'www.abip.com'
        self.long_description = 'Abip Smart Home Telnet Server'
        self.download_url = 'N/A'
        
        self.histories = {'0.0.0.0': '2010-11-30', '1.0.0.0': '2011-09-24'}
        self.version = self.histories.keys()[0]
        
        versions = self.version.split('.')
        stages = ('Alpha', 'Beta', 'Release Candidate', 'Public Release')
        if int(versions[2]) == 3:
            if int(versions[3]) == 0:
                version = '%s.%s' % (versions[0], versions[1])
            else:
                version = '%s.%s Revision %s' % (versions[0], versions[1],
                                                 versions[3])
        else:
            if int(versions[3]) == 0:
                version = '%s.%s %s' % (versions[0], versions[1],
                                        stages[int(versions[2])])
            else:
                version = '%s.%s %s %s' % (versions[0], versions[1],
                                           stages[int(versions[2])],
                                           versions[3])
        self.version_string = version
        
        self.__dict__.update(kw)

class Server(miniboa.TelnetServer):
    '''Server class
    
    @author Julot <andy.julot@gmail.com>
    @since Saturday, September 24, 2011. 02:21 PM'''
    
    PLATFORM_WIN32 = 'win32'
    PLATFORM_LINUX = 'linux'
    
    _clients = []
    _hardware = None
    _terminated = False
    _serial = None
    _platform = None
    _delay = .001
    
    def __init__(self):
        meta = Meta()
        configParser = ConfigParser()
        configParser.read('%s.conf' % meta.name.lower())
        
        port = configParser.getint('server', 'port')
        timeout = configParser.getfloat('server', 'timeout')
        super(Server, self).__init__(port=port, timeout=timeout,
                                     on_connect=self.onConnect,
                                     on_disconnect=self.onDisconnect)
        
        port = configParser.get('serial', 'port', 0)
        baudrate = configParser.getint('serial', 'baudrate')
        self._serial = Serial(port, baudrate)
        
        self._delay = configParser.getfloat('serial', 'delay')
        
        if sys.platform == self.PLATFORM_WIN32:
            self._platform = self.PLATFORM_WIN32
        else:
            self._platform = self.PLATFORM_LINUX
        
    def __del__(self):
        try: self._serial.close()
        except: pass
    
    def about(self):
        meta = Meta()
        warning('%s - %s\n' % (meta.name, meta.description))
        warning('Version %s\n' % meta.version_string)
        warning('Copyright 2011 %s\n\n' % meta.company_name)
    
    def initDb(self):
        '''Initialize database'''
        
        self.openDb()
        if not model.Device.objects.table_exists():
            model.Device.objects.create_table()
            model.Device.createIndex()
        self.closeDb()
    
    def openDb(self):
        meta = Meta()
        autorm_db.conn.connect('sqlite3', '%s.db' % meta.name.lower())
    
    def closeDb(self):
        autorm_db.conn = Database()
        
    def onConnect(self, client):
        '''Default method to handle new connection
        
        @param object client object'''
        
        client.request_wont_echo()
        self._clients.append(client)
        self.now()
        info('Connection to %s opened.\n' % client.addrport())
        
        meta = Meta()
        client.send('Welcome to %s - %s\n' % (meta.name, meta.description))
    
    def onDisconnect(self, client):
        '''Default method to handle lost connection
        
        @param object client object'''
        
        self._clients.remove(client)
        self.now()
        info('Connection to %s closed.\n' % client.addrport())
    
    def terminate(self, signum, frame):
        self._terminated = True
    
    def run(self):
        meta = Meta()
        configParser = ConfigParser()
        configParser.read('%s.conf' % meta.name.lower())
        
        info('Press Ctrl+C to stop.\n\n')
        
        port = configParser.get('serial', 'port')
        baudrate = configParser.getint('serial', 'baudrate')
        delay = configParser.getfloat('serial', 'delay')
        self.now()
        s = 'Port open to %s at %sbps with %ss delay.\n'
        info(s % (port, baudrate, delay))
        port = configParser.getint('server', 'port')
        self.now()
        info('Service run at port %s.\n' % port)
        
        # Catch linux signal.
        signal.signal(signal.SIGTERM, self.terminate)
        # Catch Ctrl+C keyboard interupt.
        try:
            while not self._terminated:
                self.poll()
                self.processClients()
        except:
            pass
        print ''
        self.now()
        info('Service at port %s stopped.\n' % port)
        self.now()
        info('Good bye.\n')
    
    def processClients(self):
        for client in self._clients:
            if client.active and client.cmd_ready:
                self.attention(client)
    
    def attention(self, client):
        command = client.get_command()
        if not command: return
        
        self.now()
        info('Receiving \'%s\' command.\n' % command)
        if command[-1] == '?':
            value = self.loadValue(command)
            self.now()
            progress('Sending response %s...' % value)
            client.send('%s\n' % value)
            success(' OK ^_^v\n')
        else:
            self.saveValue(command)
            self._serial.flush()
            self._serial.flushInput()
            self._serial.flushOutput()
            #if self._platform == self.PLATFORM_WIN32:
                #self._serial.setRTS(0)
                #time.sleep(.01)
            command = '%s\r' % command
            for char in command:
                self._serial.write(char)
                time.sleep(self._delay)
            #if self._platform == self.PLATFORM_WIN32:
                #time.sleep(.01)
                #self._serial.setRTS(1)
    
    def castInt(self, value):
        try: return int(value)
        except: return None
    
    def loadValue(self, command):
        host = self.castInt(command[0:2])
        channel = self.castInt(command[2:4])
        self.openDb()
        device = model.Device.objects.query(host=host, channel=channel)[0]
        if device: 
            return device.value
        else: 
            return '?'
        self.closeDb()
    
    def saveValue(self, command):
        value = self.castInt(command[4:])
        if value is None: return
        
        self.openDb()
        host = self.castInt(command[0:2])
        channel = self.castInt(command[2:4])
        if host and channel:
            # If host and channel defined, create a new record if not exist
            device = model.Device.objects.query(host=host, channel=channel)[0]
            if not device:
                device = model.Device(host=host, channel=channel)
            device.value = value
            device.save()
        elif host:
            # Because we will never know the channel, don't create a new record.
            # Just save the already existing channel.
            devices = model.Device.objects.query(host=host)
            for device in devices:
                device.value = value
                device.save()
        self.closeDb()
    
    def now(self):
        normal(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S '))

if __name__ == '__main__':
    colorama.init(autoreset=True)
    server = Server()
    server.about()
    server.initDb()
    server.run()
    colorama.deinit()
    